package com.ukefu.util.tts.xunfei;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import it.sauronsoftware.jave.AudioAttributes;
import it.sauronsoftware.jave.Encoder;
import it.sauronsoftware.jave.EncodingAttributes;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

/**
 * 语音合成 WebAPI 接口调用示例 接口文档（必看）：https://doc.xfyun.cn/msc_android/%E8%AF%AD%E9%9F%B3%E5%90%88%E6%88%90.html
 * webapi 合成服务参考帖子：http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38997&extra=
 * webapi是单次只支持1000个字节，具体看您的编码格式，计算一下具体支持多少文字
 * 合成发音人自动添加获取测试权限使用方法：登陆开放平台https://www.xfyun.cn/后--我的应用（必须为webapi类型应用）--添加在线语音合成（已添加的不用添加）--发音人管理---添加发音人--测试代码里需修改发音人参数
 *（Very Important）创建完webapi应用添加合成服务之后一定要设置ip白名单，找到控制台--我的应用--设置ip白名单，如何设置参考：http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=41891
 * 错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）
 * @author iflytek
 */
public class XunFeiWebTTSUtils {
	
	private static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts"; //http url 不支持解析 ws/wss schema
    private static String appid ;//到控制台-语音合成页面获取
    private static String apiSecret ;//到控制台-语音合成页面获取
    private static String apiKey ;//到控制台-语音合成页面获取
    private static String text ;
    private static String vcn ;//--添加在线语音合成（已添加的不用添加）--发音人管理---添加发音人--修改发音人参数）
    public static final Gson json = new Gson();
    
    private static String type = "internalnotice";
    private static String path = "data";
    private static String id ;
    
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(XunFeiWebTTSUtils.class);
    
    public void getTTS() throws Exception {

        // 构建鉴权url
        String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
        OkHttpClient client = new OkHttpClient.Builder().build();
        //将url中的 schema http://和https://分别替换为ws:// 和 wss://
        String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
        Request request = new Request.Builder().url(url).build();
        // 存放音频的文件
//      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//      String date = sdf.format(new Date());
        
        File logoDir = new File(path , type);
        if(!logoDir.exists()){
            logoDir.mkdirs() ;
        }
        
        final File f = new File(logoDir +"/"+ id);
        if (!f.exists()) {
            f.createNewFile();
        }else {
        	f.delete();
        }
        final FileOutputStream os = new FileOutputStream(f);
        client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                try {
                    System.out.println(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //发送数据
                JsonObject frame = new JsonObject();
                JsonObject business = new JsonObject();
                JsonObject common = new JsonObject();
                JsonObject data = new JsonObject();
                // 填充common
                common.addProperty("app_id", appid);
                //填充business
                business.addProperty("aue", "raw");
                business.addProperty("tte", "UTF8");
                business.addProperty("ent", "intp65");
                business.addProperty("vcn", vcn);//到控制台-我的应用-语音合成-添加试用或购买发音人，添加后即显示该发音人参数值，若试用未添加的发音人会报错11200
                business.addProperty("pitch", 50);
                business.addProperty("speed", 60);
                //填充data
                data.addProperty("status", 2);//固定位2
                data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes()));
                data.addProperty("encoding", "");
                //填充frame
                frame.add("common", common);
                frame.add("business", business);
                frame.add("data", data);
                webSocket.send(frame.toString());
            }
            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                //处理返回数据
//              System.out.println("receive=>" + text);
                ResponseData resp = null;
                try {
                    resp = json.fromJson(text, ResponseData.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (resp != null) {
                    if (resp.getCode() != 0) {
                    	logger.info("error=>" + resp.getMessage() + " sid=" + resp.getSid());
                        return;
                    }
                    if (resp.getData() != null) {
                        String result = resp.getData().audio;
                        byte[] audio = Base64.getDecoder().decode(result);
                        try {
                            os.write(audio);
                            os.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        if (resp.getData().status == 2) {
                            // todo  resp.data.status ==2 说明数据全部返回完毕，可以关闭连接，释放资源
                        	logger.info("TTS转换完成！音频文件保存在：" + f.getPath());
                            webSocket.close(1000, "");
                            
                            try {
								convertAudioFiles(f.getPath(),f.getPath()+".wav");
							} catch (Exception e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
                            try {
                                os.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
            }
            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                logger.info("socket closing");
            }
            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                logger.info("socket closed");
            }
            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                logger.info("connection failed");
            }
        });
    
    }
    public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        URL url = new URL(hostUrl);
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").//
                append("date: ").append(date).append("\n").//
                append("GET ").append(url.getPath()).append(" HTTP/1.1");
        Charset charset = Charset.forName("UTF-8");
        Mac mac = Mac.getInstance("hmacsha256");
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        String sha = Base64.getEncoder().encodeToString(hexDigits);
        String authorization = String.format("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().//
                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).//
                addQueryParameter("date", date).//
                addQueryParameter("host", url.getHost()).//
                build();
        return httpUrl.toString();
    }
    
    public static void convertAudioFiles(String src, String target) throws Exception {
		   FileInputStream fis = new FileInputStream(src);
		   FileOutputStream fos = new FileOutputStream(target);

		   //计算长度
		   byte[] buf = new byte[1024 * 4];
		   int size = fis.read(buf);
		   int PCMSize = 0;
		   while (size != -1) {
		      PCMSize += size;
		      size = fis.read(buf);
		    }
		   fis.close();

		   //填入参数，比特率等等。这里用的是16位单声道 8000 hz
		   WaveHeader header = new WaveHeader();
		   //长度字段 = 内容的大小（PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
		   header.fileLength = PCMSize + (44 - 8);
		   header.FmtHdrLeth = 16;
		   header.BitsPerSample = 16;
		   header.Channels = 1;
		   header.FormatTag = 0x0001;
		   header.SamplesPerSec = 14500;
		   header.BlockAlign = (short)(header.Channels * header.BitsPerSample / 8);
		   header.AvgBytesPerSec = header.BlockAlign * header.SamplesPerSec;
		   header.DataHdrLeth = PCMSize;

		   byte[] h = header.getHeader();

		   assert h.length == 44; //WAV标准，头部应该是44字节
		   //write header
		   fos.write(h, 0, h.length);
		   //write data stream
		   fis = new FileInputStream(src);
		   size = fis.read(buf);
		   while (size != -1) {
		      fos.write(buf, 0, size);
		      size = fis.read(buf);
		   }
		   
		   //音频采样率等参数转换
		   AudioAttributes audio = new AudioAttributes();
		   audio.setCodec("libmp3lame");
		   audio.setBitRate(128000);//设置比特率
		   audio.setSamplingRate(8000);
		   EncodingAttributes attrs = new EncodingAttributes();
		   attrs.setFormat("mp3");//设置格式，我的文件原本就是mp3格式的
		   attrs.setAudioAttributes(audio);
		   //attrs.setDuration(360f); // 设置截取的时长
		   Encoder encoder = new Encoder();
		   File inputFile = new File(target);
		   encoder.encode(inputFile,new File(target), attrs);
		   logger.info("格式转换结束 ----->");
		   
		   fis.close();
		   fos.close();
		}
    
    
   
    public static class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;
        public int getCode() {
            return code;
        }
        public String getMessage() {
            return this.message;
        }
        public String getSid() {
            return sid;
        }
        public Data getData() {
            return data;
        }
    }
    public static class Data {
        private int status;  //标志音频是否返回结束  status=1，表示后续还有音频返回，status=2表示所有的音频已经返回
        private String audio;  //返回的音频，base64 编码
//        private String ced;  // 合成进度
    }
	public static String getAppid() {
		return appid;
	}
	public static void setAppid(String appid) {
		XunFeiWebTTSUtils.appid = appid;
	}
	public static String getApiSecret() {
		return apiSecret;
	}
	public static void setApiSecret(String apiSecret) {
		XunFeiWebTTSUtils.apiSecret = apiSecret;
	}
	public static String getApiKey() {
		return apiKey;
	}
	public static void setApiKey(String apiKey) {
		XunFeiWebTTSUtils.apiKey = apiKey;
	}
	public static String getText() {
		return text;
	}
	public static void setText(String text) {
		XunFeiWebTTSUtils.text = text;
	}
	public static String getVcn() {
		return vcn;
	}
	public static void setVcn(String vcn) {
		XunFeiWebTTSUtils.vcn = vcn;
	}
    
	public static String getType() {
		return type;
	}
	public static void setType(String type) {
		XunFeiWebTTSUtils.type = type;
	}
	public static String getPath() {
		return path;
	}
	public static void setPath(String path) {
		XunFeiWebTTSUtils.path = path;
	}
	public static String getId() {
		return id;
	}
	public static void setId(String id) {
		XunFeiWebTTSUtils.id = id;
	}
	public static class WaveHeader {
		 
		public final char fileID[] = { 'R', 'I', 'F', 'F' };
		public int fileLength;
		public char wavTag[] = { 'W', 'A', 'V', 'E' };;
		public char FmtHdrID[] = { 'f', 'm', 't', ' ' };
		public int FmtHdrLeth;
		public short FormatTag;
		public short Channels;
		public int SamplesPerSec;
		public int AvgBytesPerSec;
		public short BlockAlign;
		public short BitsPerSample;
		public char DataHdrID[] = { 'd', 'a', 't', 'a' };
		public int DataHdrLeth;
	 
		public byte[] getHeader() throws IOException {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			writeChar(bos, fileID);
			writeInt(bos, fileLength);
			writeChar(bos, wavTag);
			writeChar(bos, FmtHdrID);
			writeInt(bos, FmtHdrLeth);
			writeShort(bos, FormatTag);
			writeShort(bos, Channels);
			writeInt(bos, SamplesPerSec);
			writeInt(bos, AvgBytesPerSec);
			writeShort(bos, BlockAlign);
			writeShort(bos, BitsPerSample);
			writeChar(bos, DataHdrID);
			writeInt(bos, DataHdrLeth);
			bos.flush();
			byte[] r = bos.toByteArray();
			bos.close();
			return r;
		}
	 
		private void writeShort(ByteArrayOutputStream bos, int s) throws IOException {
			byte[] mybyte = new byte[2];
			mybyte[1] = (byte) ((s << 16) >> 24);
			mybyte[0] = (byte) ((s << 24) >> 24);
			bos.write(mybyte);
		}
	 
		private void writeInt(ByteArrayOutputStream bos, int n) throws IOException {
			byte[] buf = new byte[4];
			buf[3] = (byte) (n >> 24);
			buf[2] = (byte) ((n << 8) >> 24);
			buf[1] = (byte) ((n << 16) >> 24);
			buf[0] = (byte) ((n << 24) >> 24);
			bos.write(buf);
		}
	 
		private void writeChar(ByteArrayOutputStream bos, char[] id) {
			for (int i = 0; i < id.length; i++) {
				char c = id[i];
				bos.write(c);
			}
		}
	}
    
}
